 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.baseadaptor;

 import java.io.File ;
 import java.io.IOException ;
 import java.net.MalformedURLException ;
 import java.net.URL ;
 import java.util.*;
 import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
 import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
 import org.eclipse.osgi.baseadaptor.hooks.*;
 import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader;
 import org.eclipse.osgi.framework.adaptor.*;
 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.framework.internal.core.Constants;
 import org.eclipse.osgi.framework.internal.protocol.bundleentry.Handler;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader;
 import org.eclipse.osgi.util.ManifestElement;
 import org.osgi.framework.*;

 /**
  * The BundleData implementation used by the BaseAdaptor.
  * @see BaseAdaptor
  * @see BundleData
  * @see StorageHook
  * @see ClassLoadingHook
  * @since 3.2
  */
 public class BaseData implements BundleData {
     private long id;
     private BaseAdaptor adaptor;
     private Bundle bundle;
     private int startLevel = -1;
     private int status = 0;
     private StorageHook[] storageHooks;
     private String location;
     private long lastModified;
     protected BundleFile bundleFile;
     private boolean dirty = false;
     protected Dictionary manifest;
     // This field is only used by PDE source lookup, and is set by a hook (bug 126517). It serves no purpose at runtime.
 protected String fileName;
     // This is only used to keep track of when the same native library is loaded more than once
 protected Collection loadedNativeCode;

     ///////////////////// Begin values from Manifest /////////////////////
 private String symbolicName;
     private Version version;
     private String activator;
     private String classpath;
     private String executionEnvironment;
     private String dynamicImports;
     private int type;

     ///////////////////// End values from Manifest /////////////////////

     /**
      * Constructs a new BaseData with the specified id for the specified adaptor
      * @param id the id of the BaseData
      * @param adaptor the adaptor of the BaseData
      */
     public BaseData(long id, BaseAdaptor adaptor) {
         this.id = id;
         this.adaptor = adaptor;
     }

     /**
      * This method calls all the configured class loading hooks {@link ClassLoadingHook#createClassLoader(ClassLoader, ClassLoaderDelegate, BundleProtectionDomain, BaseData, String[])}
      * methods until on returns a non-null value. If none of the class loading hooks returns a non-null value
      * then the default classloader implementation is used.
      * @see BundleData#createClassLoader(ClassLoaderDelegate, BundleProtectionDomain, String[])
      */
     public BundleClassLoader createClassLoader(ClassLoaderDelegate delegate, BundleProtectionDomain domain, String [] bundleclasspath) {
         ClassLoadingHook[] hooks = adaptor.getHookRegistry().getClassLoadingHooks();
         ClassLoader parent = adaptor.getBundleClassLoaderParent();
         BaseClassLoader cl = null;
         for (int i = 0; i < hooks.length && cl == null; i++)
             cl = hooks[i].createClassLoader(parent, delegate, domain, this, bundleclasspath);
         if (cl == null)
             cl = new DefaultClassLoader(parent, delegate, domain, this, bundleclasspath);
         return cl;
     }

     public final URL getEntry(String path) {
         BundleEntry entry = getBundleFile().getEntry(path);
         if (entry == null)
             return null;
         if (path.length() == 0 || path.charAt(0) != '/')
             path = '/' + path;
         try {
             //use the constant string for the protocol to prevent duplication
 return new URL (Constants.OSGI_ENTRY_URL_PROTOCOL, Long.toString(id), 0, path, new Handler(entry));
         } catch (MalformedURLException e) {
             return null;
         }
     }

     public final Enumeration getEntryPaths(String path) {
         return getBundleFile().getEntryPaths(path);
     }

     /**
      * This method calls each configured classloading hook {@link ClassLoadingHook#findLibrary(BaseData, String)} method
      * until the first one returns a non-null value.
      * @see BundleData#findLibrary(String)
      */
     public String findLibrary(String libname) {
         ClassLoadingHook[] hooks = adaptor.getHookRegistry().getClassLoadingHooks();
         String result = null;
         for (int i = 0; i < hooks.length; i++) {
             result = hooks[i].findLibrary(this, libname);
             if (result != null)
                 break;
         }
         // check to see if this library has been loaded by another class loader
 if (result != null)
             synchronized (this) {
                 if (loadedNativeCode == null)
                     loadedNativeCode = new ArrayList(1);
                 if (loadedNativeCode.contains(result)) {
                     // we must copy the library to a temp space to allow another class loader to load the library
 String temp = copyToTempLibrary(result);
                     if (temp != null)
                         result = temp;
                 } else {
                     loadedNativeCode.add(result);
                 }
             }
         return result;
     }

     private String copyToTempLibrary(String result) {
         try {
             return adaptor.getStorage().copyToTempLibrary(this, result);
         } catch (IOException e) {
             adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
         }
         return null;
     }

     public void installNativeCode(String [] nativepaths) throws BundleException {
         adaptor.getStorage().installNativeCode(this, nativepaths);
     }

     public File getDataFile(String path) {
         return adaptor.getStorage().getDataFile(this, path);
     }

     public Dictionary getManifest() throws BundleException {
         if (manifest == null)
             manifest = adaptor.getStorage().loadManifest(this);
         return manifest;
     }

     public long getBundleID() {
         return id;
     }

     public final String getLocation() {
         return location;
     }

     /**
      * Sets the location of this bundledata
      * @param location the location of this bundledata
      */
     public final void setLocation(String location) {
         this.location = location;
     }

     public final long getLastModified() {
         return lastModified;
     }

     /**
      * Sets the last modified time stamp of this bundledata
      * @param lastModified the last modified time stamp of this bundledata
      */
     public final void setLastModified(long lastModified) {
         this.lastModified = lastModified;
     }

     public void close() throws IOException {
         if (bundleFile != null)
             getBundleFile().close(); // only close the bundleFile if it already exists.
 }

     public void open() throws IOException {
         getBundleFile().open();
     }

     public final void setBundle(Bundle bundle) {
         this.bundle = bundle;
     }

     /**
      * Returns the bundle object of this BaseData
      * @return the bundle object of this BaseData
      */
     public final Bundle getBundle() {
         return bundle;
     }

     public int getStartLevel() {
         return startLevel;
     }

     public int getStatus() {
         return status;
     }

     /**
      * This method calls each configured storage hook {@link StorageHook#forgetStartLevelChange(int)} method.
      * If one returns true then this bundledata is not marked dirty.
      * @see BundleData#setStartLevel(int)
      */
     public void setStartLevel(int value) {
         startLevel = setPersistentData(value, true, startLevel);
     }

     /**
      * This method calls each configured storage hook {@link StorageHook#forgetStatusChange(int)} method.
      * If one returns true then this bundledata is not marked dirty.
      * @see BundleData#setStatus(int)
      */
     public void setStatus(int value) {
         status = setPersistentData(value, false, status);
     }

     private int setPersistentData(int value, boolean isStartLevel, int orig) {
         StorageHook[] hooks = getStorageHooks();
         for (int i = 0; i < hooks.length; i++)
             if (isStartLevel) {
                 if (hooks[i].forgetStartLevelChange(value))
                     return value;
             } else {
                 if (hooks[i].forgetStatusChange(value))
                     return value;
             }
         if (value != orig)
             dirty = true;
         return value;
     }

     public void save() throws IOException {
         adaptor.getStorage().save(this);
     }

     /**
      * Returns true if this bundledata is dirty
      * @return true if this bundledata is dirty
      */
     public boolean isDirty() {
         return dirty;
     }

     /**
      * Sets the dirty flag for this BaseData
      * @param dirty the dirty flag
      */
     public void setDirty(boolean dirty) {
         this.dirty = dirty;
     }

     public final String getSymbolicName() {
         return symbolicName;
     }

     /**
      * Sets the symbolic name of this BaseData
      * @param symbolicName the symbolic name
      */
     public final void setSymbolicName(String symbolicName) {
         this.symbolicName = symbolicName;
     }

     public final Version getVersion() {
         return version;
     }

     /**
      * Sets the version of this BaseData
      * @param version the version
      */
     public final void setVersion(Version version) {
         this.version = version;
     }

     public final int getType() {
         return type;
     }

     /**
      * Sets the type of this BaseData
      * @param type the type
      */
     public final void setType(int type) {
         this.type = type;
     }

     public final String [] getClassPath() throws BundleException {
         ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, classpath);
         return getClassPath(classpathElements);
     }

     // TODO make classpath a String[] instead of saving a comma separated string.
 public String getClassPathString() {
         return classpath;
     }

     //TODO make classpath a String[] instead of saving a comma separated string.
 public void setClassPathString(String classpath) {
         this.classpath = classpath;
     }

     public final String getActivator() {
         return activator;
     }

     /**
      * Sets the activator of this BaseData
      * @param activator the activator
      */
     public final void setActivator(String activator) {
         this.activator = activator;
     }

     public final String getExecutionEnvironment() {
         return executionEnvironment;
     }

     /**
      * Sets the execution environment of this BaseData
      * @param executionEnvironment the execution environment
      */
     public void setExecutionEnvironment(String executionEnvironment) {
         this.executionEnvironment = executionEnvironment;
     }

     public final String getDynamicImports() {
         return dynamicImports;
     }

     /**
      * Sets the dynamic imports of this BaseData
      * @param dynamicImports the dynamic imports
      */
     public void setDynamicImports(String dynamicImports) {
         this.dynamicImports = dynamicImports;
     }

     /**
      * This method calls each configured storage hook {@link StorageHook#matchDNChain(String)} method
      * until one returns true. If no configured storage hook returns true then false is returned.
      * @see BundleData#matchDNChain(String)
      */
     public final boolean matchDNChain(String pattern) {
         StorageHook[] hooks = getStorageHooks();
         for (int i = 0; i < hooks.length; i++)
             if (hooks[i].matchDNChain(pattern))
                 return true;
         return false;
     }

     /**
      * Returns the adaptor for this BaseData
      * @return the adaptor
      */
     public final BaseAdaptor getAdaptor() {
         return adaptor;
     }

     /**
      * Returns the BundleFile for this BaseData. The first time this method is called the
      * configured storage {@link BaseAdaptor#createBundleFile(Object, BaseData)} method is called.
      * @return the BundleFile
      * @throws IllegalArgumentException
      */
     public synchronized BundleFile getBundleFile() throws IllegalArgumentException {
         if (bundleFile == null)
             try {
                 bundleFile = adaptor.createBundleFile(null, this);
             } catch (IOException e) {
                 throw new IllegalArgumentException (e.getMessage());
             }
         return bundleFile;
     }

     private static String [] getClassPath(ManifestElement[] classpath) {
         if (classpath == null) {
             if (Debug.DEBUG && Debug.DEBUG_LOADER)
                 Debug.println(" no classpath"); //$NON-NLS-1$
 /* create default BundleClassPath */
             return new String [] {"."}; //$NON-NLS-1$
 }

         ArrayList result = new ArrayList(classpath.length);
         for (int i = 0; i < classpath.length; i++) {
             if (Debug.DEBUG && Debug.DEBUG_LOADER)
                 Debug.println(" found classpath entry " + classpath[i].getValueComponents()); //$NON-NLS-1$
 String [] paths = classpath[i].getValueComponents();
             for (int j = 0; j < paths.length; j++) {
                 result.add(paths[j]);
             }
         }

         return (String []) result.toArray(new String [result.size()]);
     }

     /**
      * Returns the storage hook which is keyed by the specified key
      * @param key the key of the storage hook to get
      * @return the storage hook which is keyed by the specified key
      */
     public StorageHook getStorageHook(String key) {
         if (storageHooks == null)
             return null;
         for (int i = 0; i < storageHooks.length; i++)
             if (storageHooks[i].getKey().equals(key))
                 return storageHooks[i];
         return null;
     }

     /**
      * Sets the instance storage hooks for this base data. This is method
      * may only be called once for the lifetime of the base data. Once set,
      * the list of storage hooks remains constant.
      * @param storageHooks the storage hook to add
      */
     public void setStorageHooks(StorageHook[] storageHooks) {
         if (this.storageHooks != null)
             return; // only allow this to be set once.
 this.storageHooks = storageHooks;
     }

     /**
      * Returns all the storage hooks associated with this BaseData
      * @return all the storage hooks associated with this BaseData
      */
     public StorageHook[] getStorageHooks() {
         return storageHooks == null ? new StorageHook[0] : storageHooks;
     }

     /**
      * Gets called by BundleFile during {@link BundleFile#getFile(String, boolean)}. This method
      * will allocate a File object where content of the specified path may be
      * stored for the current generation of the base data. The returned File object may
      * not exist if the content has not previously be stored.
      * @param path the path to the content to extract from the base data
      * @return a file object where content of the specified path may be stored.
      */
     public File getExtractFile(String path) {
         return adaptor.getStorage().getExtractFile(this, path);
     }

     /**
      * This is only used to support PDE source lookup. The field named &quot;fileName&quot;
      * must be set for PDE to access the absolute path string.
      * @param fileName an absolute path string to the base bundle file.
      */
     // This is only done for PDE source lookup (bug 126517)
 public void setFileName(String fileName) {
         this.fileName = fileName;
     }

     
     /**
      * Return a string representation of the bundle that can be used in debug messages.
      *
      * @return String representation of the bundle
      */
     public String toString() {
         String name = getSymbolicName();
         if (name == null)
             return getLocation();
         Version ver = getVersion();
         if (ver == null)
             return name;
         return name+"_"+ver; //$NON-NLS-1$
 }
 }

